package com.codeyang.config;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.codeyang.filter.JWTCheckFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 描述: TO-删除标志-DO
 *
 * @author CodeYang_Site
 * @version 2021/5/18 13:23
 */
@Component
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 用户JWT token 过滤器
     */
    @Autowired
    private JWTCheckFilter jwtCheckFilter;

    /**
     * redis操作
     */
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 接管认证-登录配置
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //手动配一下一些登录账号和密码 省的联系数据库
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password(passwordEncoder().encode("123"))
                .authorities("sys:add", "sys:del")
                .and()
                .withUser("test")
                .password(passwordEncoder().encode("123"))
                .authorities("sys:add");

    }


    /**
     * 接管请求配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭跨站请求伪造验证
        http.csrf().disable();
        //手动指定我们自己的JWTTokenFilter 在用户登录认证之前
        http.addFilterBefore(jwtCheckFilter, UsernamePasswordAuthenticationFilter.class);
        //放行登录接口 和指定登录接口处理器
        http.formLogin()
                .successHandler(successHandler())
                .loginProcessingUrl("/doLogin")
                .permitAll();
        //设置其他请求全部拦截
        http.authorizeRequests()
                .anyRequest()
                .authenticated();
    }

    /**
     * 登录成功的处理其
     * 1.将用户对象转为JWT交给前端
     * 2.设置一些时间值....
     * 3.写出去
     *
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler successHandler() {
        return (request, response, authentication) -> {
            //1设置编码
            response.setContentType("application/json;charset=utf-8");
            //2.拿到用户身份,也就是用户名
            // security的用户名
            User principal = (User) authentication.getPrincipal();
            String username = principal.getUsername();
            //3.拿到权限 --- mysql 操作已经在loaduser里面完成了权限设置
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            //4.转换权限,因为JWT里存储的是字符串
            List<String> auths = authorities.stream()
                    .map(Object::toString)
                    .collect(Collectors.toList());
            //5.组装JWT
            //5-1头部
            HashMap<String, Object> header = new HashMap<>(4);
            //固定写法
            header.put("alg", "HS256");
            header.put("typ", "JWT");
            //得到与设置 创建时间和过期时间
            Calendar now = Calendar.getInstance();
            Date createTime = now.getTime();
            //两小时后
            now.add(Calendar.SECOND, 7200);
            Date expireTime = now.getTime();

            //设置载荷 body
            String jwt = JWT.create()
                    .withHeader(header)
                    .withIssuedAt(createTime)
                    .withExpiresAt(expireTime)
                    .withClaim("username", username) //设置用户名
                    .withClaim("auths", auths) //设置权限
                    .sign(Algorithm.HMAC256("SECURITY-JWT-KEY"));

            // 写出去了
            HashMap<String, Object> map = new HashMap<>(8);
            map.put("code", 200);
            map.put("msg", "OK");
            map.put("access_token", jwt);
            map.put("type", "bearer");
            map.put("expire_in", 7200);
            // 存入redis  为了做登出  除了可以做业务的控制登出外 当需要大规模登出的时候 也可以改验证签名
            redisTemplate.opsForValue().set("jwt:token:" + jwt, "", Duration.ofSeconds(7200));


            ObjectMapper objectMapper = new ObjectMapper();
            String json = objectMapper.writeValueAsString(map);
            PrintWriter writer = response.getWriter();
            writer.write(json);
            writer.flush();
            writer.close();

        };
    }


    /**
     * 必须配置的密码匹配器
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
